import { FileInfo } from 'src/core/system/minio/entities/file_info.entity';
/*
 * @Author: c
 * @Date: 2024-01-15 16:12:27
 * @LastEditTime: 2024-03-17 21:35:59
 * @LastEditors: c
 * @Description: 
 * @FilePath: \music-web-backend\src\core\system\minio\minio.service.ts
 * https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { randomUUID } from 'crypto';
import * as dayjs from 'dayjs';
import { MinioService } from 'nestjs-minio-client';
import { EXCEPTION } from 'src/common/const/constants';
import { ConfigEnum } from 'src/common/enums/config.enum';
import { CommonException } from 'src/common/exception/common.exception';
import { UploadFile } from 'src/common/type/file';
import { In, Repository } from 'typeorm';
import { FileDescDto } from './dto/create-file.dto';
import { StatusTypeEnum } from './enum';
const Ffmpeg = require('fluent-ffmpeg')



@Injectable()
export class MinioClientService {

  constructor(
    private readonly minioClientService: MinioService,
    private readonly configService: ConfigService,
    @InjectRepository(FileInfo) private fileInfoRepository: Repository<FileInfo>,
  ) { }

  /**
   * 文件-根据名称-获取上传链接
   * @param req 
   * @param fileName 
   * @returns 
   */
  getPresignedUrlByName(req: any, fileName: string, isPublic: boolean = true): Promise<{ url: string, objectName?: string }> {
    const user = req?.user?.username ?? '游客'
    const day = dayjs().format('YYYY-MM-DD')
    const objName = `${user}/${day}/${randomUUID()}-${fileName}`
    const bucket = isPublic ? this.configService.get(ConfigEnum.MINIO_PUBLIC_BUCKET) : this.configService.get(ConfigEnum.MINIO_PRIVATE_BUCKET)
    return new Promise<any>((resolve, reject) => {
      this.minioClientService.client.presignedUrl('PUT', bucket, objName, 60 * 60, function (err, presignedUrl) {
        if (err) {
          console.log(err)
          Logger.error(err);
          reject(err)
        }

        Logger.log(presignedUrl)

        resolve({ url: presignedUrl, objectName: objName })

      })
    })
  }


  /**
   * 获取文件上传链接
   * @param objectName 
   * @returns 
   */
  async getPresignedUrl(req, file: FileDescDto) {

    const user = req!.user!

    const day = dayjs().format('YYYY-MM-DD')
    // const localDate = new Date().toLocaleDateString()
    const objName = `${user.username}/${day}/${randomUUID()}-${file.name}`
    // const buket = `public`
    const o = plainToClass(FileInfo, file);

    o.objectName = objName;
    o.creator = user.username
    o.deleted = false
    o.bucket = file.isPublic ? this.configService.get(ConfigEnum.MINIO_PUBLIC_BUCKET) : this.configService.get(ConfigEnum.MINIO_PRIVATE_BUCKET)
    // o.id = `${o.creator} ${o.uid} ${o.name}`

    if (!o.bucket) {
      throw new CommonException(EXCEPTION.COMMON)
    }

    let data: FileInfo;

    try {
      data = await this.fileInfoRepository.save(o)

      // const result: InsertResult = await this.fileInfoRepository.createQueryBuilder()
      //   .insert()
      //   .into(FileInfo)
      //   .values(o)
      //   .orUpdate(
      //     [
      //       "percentage",
      //       "status",
      //       "size",
      //       "url",
      //       "type",
      //       "type",
      //       "last_modified",
      //       'deleted'
      //     ], //需要重写的字段
      //     ["creator", "name", "uid"], //冲突的字段
      //   )
      //   .execute()

    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

    return new Promise<any>((resolve, reject) => {
      this.minioClientService.client.presignedUrl('PUT', o.bucket, objName, 60 * 60, function (err, presignedUrl) {
        if (err) {
          console.log(err)
          Logger.error(err);
          reject(err)
        }

        Logger.log(presignedUrl)

        resolve({ url: presignedUrl, fileId: data.id })

      })
    })
  }

  /**
   * 获取临时访问链接
   * @param buket 
   * @param objectName 
   * @param expiredTime 
   * @returns 
   */
  presignedGetObject(buket: string = 'private', objectName: string, expiredTime: number = 2 * 60 * 60) {

    return new Promise<any>((resolve, reject) => {
      this.minioClientService.client.presignedGetObject(buket, objectName, expiredTime, function (err, presignedUrl) {
        if (err) {
          Logger.error(err);
          reject(err)
        }

        Logger.log(presignedUrl)

        resolve({ url: presignedUrl })

      })
    })
  }


  /**
   * 文件-通过Multer上传
   * @param req 
   * @param file 
   * @returns 
   */
  async create(req: any, file: Express.Multer.File, isPublic: boolean = true): Promise<UploadFile> {
    const { username } = req.user
    let uploadFileInfo = new UploadFile()
    let fileInfo = new FileInfo(undefined)
    try {

      const { url, objectName } = await this.getPresignedUrlByName(req, file.originalname, isPublic);
      await fetch(url, { method: 'put', body: file.buffer, headers: { "Content-Type": file.mimetype } })
      const addUrl = url.split("?").at(0);
      fileInfo.bucket = isPublic ? 'public' : 'private'
      // fileInfo.mimeType = file.mimetype
      fileInfo.size = file.size
      fileInfo.creator = username
      fileInfo.url = !isPublic ? '' : addUrl
      fileInfo.objectName = objectName
      fileInfo.name = file.originalname
      fileInfo.percentage = 100
      fileInfo.status = StatusTypeEnum.SUCCESS
      const newFileInfo = await this.fileInfoRepository.save(fileInfo);
      uploadFileInfo.url = isPublic ? addUrl : (await this.presignedGetObject('private', objectName)).url;
      uploadFileInfo.id = newFileInfo.id;

    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

    return uploadFileInfo
  }

  findAll() {
    return `This action returns all minio`;
  }

  async findOne(id: string) {
    return await this.fileInfoRepository.findOne({ where: { id } })
  }

  async update(req, fileDescDto: FileDescDto) {

    await this.fileInfoRepository.createQueryBuilder().update(FileInfo)
      .set(fileDescDto)
      .where("uid = :uid and creator = :creator and name = :name", { uid: fileDescDto.uid, creator: req.user.username, name: fileDescDto.name })
      .execute()
  }

  /**
   * 根据对象名称-删除对象
   * @param objectName 
   */
  async remove(req, file: FileDescDto) {

    try {

      const fileData = await this.fileInfoRepository.findOne({ where: { uid: file.uid, creator: req.user.username, name: file.name } });
      if (fileData && fileData.deleted) {
        // await this.fileInfoRepository.createQueryBuilder().update(FileInfo)
        //   .set({ deleted: 1 })
        //   .where("uid = :uid and creator = :creator and name = :name", { uid: fileData.uid, creator: fileData.creator, name: fileData.name })
        //   .execute()

        await this.fileInfoRepository
          .createQueryBuilder()
          .delete()
          .from(FileInfo)
          .where("uid = :uid and creator = :creator and name = :name", { uid: fileData.uid, creator: fileData.creator, name: fileData.name })
          .execute()
        await this.minioClientService.client.removeObject('public', fileData.objectName)
      }

    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

  }


  /**
   * 文件-根据id-删除
   * @param id 
   */
  async deleteFileById(id: string) {
    const { bucket, objectName } = await this.fileInfoRepository.findOne({ where: { id } })

    try {
      if (bucket && objectName) {
        await this.removeObject(bucket, objectName)
        await this.fileInfoRepository.delete(id)
      }
    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

  }
  /**
   * 文件-根据id数组-删除
   * @param ids 
   */
  async deleteFileByIds(ids: string[]) {
    const fileInfos = await this.fileInfoRepository.findBy({ id: In(ids) })
    try {
      if (fileInfos.length > 0) {
        for await (const { bucket, objectName } of fileInfos) {
          await this.removeObject(bucket, objectName)
        }
        await this.fileInfoRepository.delete(ids)
      }
    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

  }


  /**
 * 文件-根据对象名称-删除
 * @param bucket 存储桶名称
 * @param objectName 对象名称
 */
  async removeObject(bucket: string, objectName: string) {

    try {

      await this.minioClientService.client.removeObject(bucket, objectName)

    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

  }


}

